//COE 1186 
//Team Autolytics
//April 5th
//Multithreaded Web Server


        import java.io.* ;
        import java.net.* ;
        import javax.net.ssl.*;
        import java.util.* ;
        import java.sql.*;
        import java.util.concurrent.Semaphore;
        import java.security.*;
        import javax.crypto.*;
        public final class Server
        {
            //Database variables
            private static final int numThreads = 100;
            protected static Connection dbConnection = null;
            protected static String connectString = "jdbc:mysql://ec2-184-73-238-96.compute-1.amazonaws.com:3008/AutoBiography";
            protected static String driver = "com.mysql.jdbc.Driver";
            protected static String userid = "Team_AutoLytics";
            protected static String pwd = "COE1186";
            protected static Statement stmnt = null;
            public static Semaphore lockJobs;
            protected static ResultSet r = null;
            protected static Vector<Job> jobQueue;
            static final int port = 3226;
            protected static int numjobs;
                public static void main(String argv[]) throws Exception
                {
                    new Server();
                }
        
            public Server(){
                try 
                {
                    //Establishing a connection with our online database.
                    System.out.println("Starting...");
                    try{
                        Class.forName(driver).newInstance();
                        dbConnection = DriverManager.getConnection(connectString, userid, pwd);
                        stmnt=dbConnection.createStatement();
                        System.out.println("Connection successful");
                    }catch(Exception connectexcept){
                        System.out.println("Connection Error.");
                        System.out.println(connectexcept);
                        System.exit(0);
                    }
                    
                    //Get localhost address to set up a socket connection.
                    InetAddress addr =
//                    InetAddress.getByName("192.168.1.1");
                    InetAddress.getLocalHost();
                    System.out.println("Server address = " + addr);
                   ServerSocket ss = new ServerSocket(port);
                    System.out.println("ServerSocket at port " + port);
                    Socket socket = null;
                    lockJobs = new Semaphore(1);
                    jobQueue= new Vector<Job>(numThreads);
                    numjobs = 0;
                    jobQueue.setSize(100);
                    while(true){
                        try{
                            socket = ss.accept();
                        }
                        catch(Exception acceptexcept){
                            System.out.println("Some zany error:\n " + acceptexcept);
                        }
                        System.out.println(" Connected to socket: " + socket.getInetAddress() + ":" + socket.getLocalPort());
                    

                    
                    //Vector is a sychronized collection, so it is ideal for threading.

                        if (socket != null){
                            System.out.println("going");
                            int index;
                            index = 0;
                            if (index < numThreads && jobQueue.get(index) != null){
                                index ++;
                            }
                            else if (index <numThreads){
                     /*           try{
                                    lockJobs.acquire();
                                    jobQueue.add(new Job(socket));
                                    lockJobs.release();
                                }
                                catch(Exception lockexcept){
                                    System.out.println("Couldn't acquire semaphore.");
                                }*/
                                jobQueue.add(new Job(socket));
                            }
                            System.out.println("Went");
                        }
                    }
                }
                catch( Exception e) 
                {
                    System.out.println("Web server error " + e);
                    e.printStackTrace();
                }

            }

            private class Job implements Runnable{
                private Request work;
                protected SecureLogin safe;
                protected ObjectInputStream in;
                protected ObjectOutputStream out;
                protected OutputStream outstr;
                protected InputStream instr;
                protected Socket socket;
                protected PublicKey theirkey;
                protected PrivateKey myprivkey;
                public Job(Socket sock){
                    socket = sock;
                    // Use a ObjectInputStream on the socket's InputStream to read the
                    // string sent by the server
                    Request req = null;
                    try{
                        instr = socket.getInputStream();
                        in = new ObjectInputStream(instr);
                        theirkey = (PublicKey) in.readObject();
                        KeyPair mypair = KeyPairGenerator.getInstance(Xipher.algo).genKeyPair();
                        PublicKey mypubkey = mypair.getPublic();
                        myprivkey = mypair.getPrivate();
                        outstr = socket.getOutputStream();
                        out = new ObjectOutputStream(outstr);
                        out.writeObject(mypubkey);
                        Cipher myDCipher = new Xipher().getDCipher(myprivkey);
                        in = new ObjectInputStream(instr);
                        CipherInputStream cstream = new CipherInputStream(in, myDCipher);;
                        Cipher myCipher = new Xipher().getCipher(theirkey);
                        System.out.println("Made input stream...");
                        out = new ObjectOutputStream(outstr);
                        CipherOutputStream coutstream = new CipherOutputStream(out, myCipher);
                        System.out.print("Receiving request: ");
                        req = (Request) in.readObject();
                    }
                    catch(Exception inoutexcept){
                        System.out.println("In out except\n" + inoutexcept);
                    }
                    if (req != null){
                        work = req;
                        this.run();
                    }
                }
                public void run(){
                    safe = new SecureLogin();
                    Response resp = null;
                    boolean passed = authenticate(work, safe);
                    if (passed){
                        ResultSet rs = querydatabase(work.getChoice(), work);
                        resp = new Response();
                        if (rs == null){
                            System.out.println("Rs is null");
                        }
                        resp.addResults(rs);
  
   
                    }
                    try{
                  //  out =
                   // new ObjectOutputStream(socket.getOutputStream());
                    System.out.println("Writing results");
                    out.writeObject(resp);
                    }
                    catch(Exception wbexcept){
                        System.out.println("Problem sending response: \n" + wbexcept);
                    }
                   try{
                      work = (Request) in.readObject();
                      
                      this.run();
                   }
                   catch(Exception ioexcept){
                      System.out.println("IO except.");
                   }
                }
                public ResultSet querydatabase(int option, Request usersent){                    
                    
                    /*
                     choice 1 :@none    Creates table Users
                     choice 2 :@none    Creates table Cars
                     choice 3 :@none    Creates table Stats
                     choice 4 :@UID     Select * from Users where UID == @arg0
                     choice 5 :@none    Creates table CH_Data
                     choice 6 :@f,l,uid,pass,salt,type,CH_ID, Integer NUMVEHICLES
                     "INSERT INTO USERS (FIRSTNAME, LASTNAME, UID, PASSWORD, SALT, USERTYPE, CH_ID, NUMVEHICLES) VALUES (?,?,?,?,?,?,?,?)"
                     choice 7 :@UID     Select * from Cars where User = @arg0
                     choice 8 :@make, model, year, vin, Username, CHID
                     "INSERT INTO CARS (MAKE, MODEL, YEAR, VIN, USER, CH_ID) VALUES(?, ?, ?, ?, ?, ?)"
                     choice 9 :@UID     Numvehicles is incremented
                     choice 10:@VIN     Select * from Stats where car = VIN
                     choice 11:@newUID,hash, olduid 
                            "UPDATE USERS SET UID = ?,PASSWORD = ? WHERE UID = ?"
                     choice 12:@newuid, olduid 
                            "UPDATE CARS SET USER = ? WHERE USER = ?"
                     choice 13:@ch_id   "SELECT * FROM CARS WHERE CH_ID = @arg0
                     choice 14:@vin     "SELECT * FROM CLEARINGHOUSE WHERE VIN = ? "
                     choice 15:@vin     "DELETE FROM CLEARINGHOUSE WHERE VIN = '"+vin+"'"
                     choice 16:@vin,date 
                        "DELETE FROM CLEARINGHOUSE WHERE VIN = '"+currCar.vin+"' AND DATE_TIME = '"+slot+"'"
                     choice 17:@vin     "DELETE FROM CARS WHERE VIN = ?"
                     choice 18:@UID     "UPDATE USERS SET NUMVEHICLES = NUMVEHICLES-1 WHERE UID = ?"
                     choice 19:@vin, date
                                        "SELECT * FROM STATS WHERE VIN = ? ORDER BY 'DATE_TIME' ASC"
                     choice 20:@vin      "SELECT * FROM STATS WHERE VIN = ? ORDER BY 'ORDER_NUM' ASC"
            

                     */
                    try{
                    
                    switch (option) {
                        case 1: System.out.println("Statement goes here.");       
                            PreparedStatement st1 = dbConnection.prepareStatement(
                            "CREATE TABLE USERS (UID VARCHAR(100) PRIMARY KEY, PASSWORD VARCHAR(44) NOT NULL, SALT VARCHAR(32) NOT NULL, FIRSTNAME VARCHAR(15), LASTNAME VARCHAR(15), USERTYPE VARCHAR(15), CH_ID VARCHAR(10), NUMVEHICLES INT)");
                            st1.execute();
                            return null;
                        case 2: ;
                            PreparedStatement st2 = dbConnection.prepareStatement("CREATE TABLE CARS (VIN VARCHAR(17), MAKE VARCHAR(15), MODEL VARCHAR(20), YEAR VARCHAR(4), USER VARCHAR(100), CH_ID VARCHAR(10), Primary Key (VIN), Foreign Key (USER) references AUTOBIOGRAPHY.USERS(UID) ON UPDATE CASCADE ON DELETE CASCADE)");
                            st2.execute();
                            return null;
                        case 3: ;                            
                            PreparedStatement st3 = dbConnection.prepareStatement( "CREATE TABLE STATS (RPM INT, ENGINE_TEMP INT, TIRE_PRESSURE INT, FUEL_ECONOMY FLOAT, MILEAGE INT, OIL_LIFE FLOAT, FUEL_LEVEL FLOAT, TRANS_STATUS VARCHAR(15), TIME_IN_N FLOAT, TIME_IN_D FLOAT, TIME_IN_R FLOAT, TIME_IN_1 FLOAT, TIME_IN_2 FLOAT, TIME_IN_3 FLOAT, INFO_STATUS VARCHAR(10), RADIO_FREQ FLOAT, CONNECTED VARCHAR(3), CD VARCHAR(3), IPOD VARCHAR(3), INTERNAL_TEMP INT, EXTERNAL_TEMP INT, FAN_SPEED VARCHAR(6), CLIMATE_STATUS VARCHAR(10), PHONE_CON VARCHAR(3), CALL_TIME INT, COM_STATUS VARCHAR(10), HEADLIGHT_STATUS VARCHAR(6), POWER_OUTLETS VARCHAR(10), INTERNAL_LIGHTS VARCHAR(10), SUSP_STATUS VARCHAR(10), BRAKE_FLUID_LIFE INT, BRAKE_STATUS VARCHAR(10), STEERING_STATUS VARCHAR(10), ORDER_NUM INT, DATE_TIME VARCHAR(25), VIN VARCHAR(17), Primary Key (VIN, DATE_TIME))");
                            st3.execute();
                            return null;
                        case 4: ;   
                            PreparedStatement st4 = dbConnection.prepareStatement("SELECT * FROM USERS WHERE UID = ?");
                            st4.setString(1, (String) work.getArgs()[0]);
                            System.out.println("Set UID arg to " + (String) work.getArgs()[0]);
                            ResultSet rs = st4.executeQuery();
                            return rs;
                        case 5: ;  
                            PreparedStatement st5 = dbConnection.prepareStatement("CREATE TABLE CLEARINGHOUSE (RPM INT, ENGINE_TEMP INT, TIRE_PRESSURE INT, FUEL_ECONOMY FLOAT, MILEAGE INT, OIL_LIFE FLOAT, FUEL_LEVEL FLOAT, TRANS_STATUS VARCHAR(15), TIME_IN_N FLOAT, TIME_IN_D FLOAT, TIME_IN_R FLOAT, TIME_IN_1 FLOAT, TIME_IN_2 FLOAT, TIME_IN_3 FLOAT, INFO_STATUS VARCHAR(10), RADIO_FREQ FLOAT, CONNECTED VARCHAR(3), CD VARCHAR(3), IPOD VARCHAR(3), INTERNAL_TEMP INT, EXTERNAL_TEMP INT, FAN_SPEED VARCHAR(6), CLIMATE_STATUS VARCHAR(10), PHONE_CON VARCHAR(3), CALL_TIME INT, COM_STATUS VARCHAR(10), HEADLIGHT_STATUS VARCHAR(6), POWER_OUTLETS VARCHAR(10), INTERNAL_LIGHTS VARCHAR(10), SUSP_STATUS VARCHAR(10), BRAKE_FLUID_LIFE INT, BRAKE_STATUS VARCHAR(10), STEERING_STATUS VARCHAR(10), ORDER_NUM INT, DATE_TIME VARCHAR(25), VIN VARCHAR(17), CH_ID VARCHAR(10), Primary Key (VIN, DATE_TIME, CH_ID))");
                            st5.execute();
                            return null;
                        case 6: ;    
                            PreparedStatement st6 = dbConnection.prepareStatement("INSERT INTO USERS (FIRSTNAME, LASTNAME, UID, PASSWORD, SALT, USERTYPE, CH_ID, NUMVEHICLES) VALUES (?,?,?,?,?,?,?,?)");
                            st6.setString(1, (String) work.getArgs()[0]);
                            st6.setString(2, (String) work.getArgs()[1]);
                            st6.setString(3, (String) work.getArgs()[2]);
                            st6.setString(4, (String) work.getArgs()[3]);
                            st6.setString(5, (String) work.getArgs()[4]);
                            st6.setString(6, (String) work.getArgs()[5]);
                            st6.setString(7, (String) work.getArgs()[6]);
                            st6.setInt(8, ((Integer) work.getArgs()[7]).intValue());
                            st6.executeUpdate();
                            return null;
                        case 7: ;  
                            PreparedStatement st7 = dbConnection.prepareStatement("SELECT * FROM CARS WHERE USER = ?");
                            st7.setString(1, (String) work.getArgs()[0]);
                            ResultSet rs7 = st7.executeQuery();
                            return rs7;
                        case 8: ;        
                            PreparedStatement st8 = dbConnection.prepareStatement( "INSERT INTO CARS (MAKE, MODEL, YEAR, VIN, USER, CH_ID) VALUES(?, ?, ?, ?, ?, ?)");
                            st8.setString(1, (String) work.getArgs()[0]);
                            st8.setString(2, (String) work.getArgs()[1]);
                            st8.setString(3, (String) work.getArgs()[2]);
                            st8.setString(4, (String) work.getArgs()[3]);
                            st8.setString(5, (String) work.getArgs()[4]);
                            st8.setString(6, (String) work.getArgs()[5]);
                            st8.executeUpdate();
                            return null;
                        case 9: ;       
                            PreparedStatement st9 = dbConnection.prepareStatement("UPDATE USERS SET NUMVEHICLES = NUMVEHICLES+1 WHERE UID = ?");
                            st9.setString(1, (String) work.getArgs()[0]);
                            st9.executeUpdate();
                            return null;
                        case 10: ;       
                            PreparedStatement st10 = dbConnection.prepareStatement("SELECT * FROM STATS WHERE VIN = ?");
                            st10.setString(1, (String) work.getArgs()[0]);
                            ResultSet rs10 = st10.executeQuery();
                            return rs10;

                        case 11: ; 
                            PreparedStatement st11 = dbConnection.prepareStatement("UPDATE USERS SET UID = ?,PASSWORD = ? WHERE UID = ?");
                            st11.setString(1, (String) work.getArgs()[0]);
                            st11.setString(2, (String) work.getArgs()[1]);
                            st11.setString(3, (String) work.getArgs()[2]);
                            st11.executeUpdate();
                            return null;
                        case 12: ; 
                            PreparedStatement st12 = dbConnection.prepareStatement( "UPDATE CARS SET USER = ? WHERE USER = ?");
                            st12.setString(1, (String) work.getArgs()[0]);
                            st12.setString(2, (String) work.getArgs()[1]);
                            st12.executeUpdate();
                            return null;
                        case 13: ;      
                            PreparedStatement st13 = dbConnection.prepareStatement( "SELECT * FROM CARS WHERE CH_ID = ?");
                            st13.setString(1, (String) work.getArgs()[0]);
                            ResultSet rs13 = st13.executeQuery();
                            return rs13;
                           
                        case 14: ;     
                            PreparedStatement st14 = dbConnection.prepareStatement("SELECT * FROM CLEARINGHOUSE WHERE VIN = ? ");
                            st14.setString(1, (String) work.getArgs()[0]);
                            ResultSet rs14 = st14.executeQuery();
                            return rs14;
                        case 15: ;      
                            PreparedStatement st15 = dbConnection.prepareStatement( "DELETE FROM CLEARINGHOUSE WHERE VIN = ?");
                            st15.setString(1, (String) work.getArgs()[0]);
                            st15.executeUpdate();
                            return null;
                        case 16: ;    
                            PreparedStatement st16 = dbConnection.prepareStatement( "DELETE FROM CLEARINGHOUSE WHERE VIN = ? AND DATE_TIME = ?");
                            st16.setString(1, (String) work.getArgs()[0]);
                            st16.setString(2, (String) work.getArgs()[1]);
                            st16.executeUpdate();
                            return null;
                        case 17: ;   
                            PreparedStatement st17 = dbConnection.prepareStatement( "DELETE FROM CARS WHERE VIN = ?");
                            st17.setString(1, (String) work.getArgs()[0]);
                            st17.executeUpdate();
                            return null;    
                        case 18: ;
                            PreparedStatement st18 = dbConnection.prepareStatement("UPDATE USERS SET NUMVEHICLES = NUMVEHICLES-1 WHERE UID = ?");
                            st18.setString(1, (String) work.getArgs()[0]);
                            st18.executeUpdate();
                            return null;
                        case 19:  ;
                            PreparedStatement st19 = dbConnection.prepareStatement( "SELECT * FROM STATS WHERE VIN = ? ORDER BY 'DATE_TIME' ASC");
                            st19.setString(1, (String) work.getArgs()[0]);
                            ResultSet rs19 = st19.executeQuery();
                            return rs19;
                        case 20:  ;
                            PreparedStatement st20 = dbConnection.prepareStatement("SELECT * FROM STATS WHERE VIN = ? ORDER BY 'ORDER_NUM' ASC");
                            st20.setString(1, (String) work.getArgs()[0]);
                            ResultSet rs20 = st20.executeQuery();
                            return rs20;
                        case 21:  ;
                            
                            
                            return null;
                        default: ;     return null;
                    }
                        
                        
                    }
                    catch(Exception switchexcept){
                        System.out.println("Problem with querying database\n" + switchexcept);
                        return null;
                    }
                }
                public boolean authenticate(Request req, SecureLogin safe){
                    if ((1 <= req.getChoice() && req.getChoice() <=  3 )||  req.getChoice() == 5){ // Just creating tables, not an issue
                        return true;
                    }
                    boolean toret = false;
                    try{
                        toret = safe.authenticate(dbConnection , work.getUser().getUserName(), work.getUser().getPass());
                    }
                    catch(Exception authentexcept){
                        System.out.println("Error authenticating.");
                        return false;
                    }
                    return toret;
                }
            }
}


